﻿using Apewer.Network;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

namespace Apewer
{

    /// <summary></summary>
    public class NetworkUtility
    {

        #region UDP

        /// <summary>唤醒局域网中拥有指定 MAC 地址的设备。</summary>
        /// <param name="mac">被唤醒设备的 MAC 地址，必须是长度为 6 的字节数组。</param>
        public static void WakeOnLan(byte[] mac)
        {
            if (mac.Length != 6) return;

            var uc = new System.Net.Sockets.UdpClient();
            uc.Connect(IPAddress.Broadcast, 65535);

            var pack = new List<byte>();

            // 前 6 字节为 0xFF。
            for (int i = 0; i < 6; i++) pack.Add(255);

            // 目标 MAC 地址重复 16 次。
            for (int i = 0; i < 16; i++)
            {
                for (int j = 0; j < 6; j++) pack.Add(mac[j]);
            }

            // 发送 102 字节数据。
            uc.Send(pack.ToArray(), pack.Count);

            uc.Close();
        }

        /// <summary>从 NTP 服务器获取 UTC 时间。</summary>
        /// <remarks>
        /// 通用 NTP 服务器：<br/>
        /// Worldwide: pool.ntp.org<br/>
        /// Asia: asia.pool.ntp.org<br/>
        /// China: edu.ntp.org.cn<br/>
        /// China: us.ntp.org.cn<br/>
        /// Europe: europe.pool.ntp.org<br/>
        /// North: America north-america.pool.ntp.org<br/>
        /// Oceania: oceania.pool.ntp.org<br/>
        /// South America: south-america.pool.ntp.org<br/>
        /// Windows: time.windows.com<br/>
        /// Windows: time.nist.gov<br/>
        /// </remarks>
        public static Class<DateTime> GetUtcFromNtp(string server = "pool.ntp.org", int port = 123, int timeout = 1000)
        {
            try
            {
                var ipa = null as IPAddress;
                var parseIP = IPAddress.TryParse(server, out ipa);
                if (!parseIP)
                {
                    var addresses = Dns.GetHostEntry(server).AddressList;
                    if (addresses.Length > 0) ipa = addresses[0];
                }
                if (ipa == null) return null;

                var endpoint = new IPEndPoint(ipa, port);
                return GetUtcFromNtp(endpoint, timeout);
            }
            catch { }
            return null;
        }

        /// <summary>从 NTP 服务器获取 UTC 时间。</summary>
        public static Class<DateTime> GetUtcFromNtp(IPEndPoint endpoint, int timeout = 1000)
        {
            try
            {
                var request = new byte[48];
                request[0] = 0x1B;

                var response = new byte[48];
                response[0] = 0x1B;

                var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                socket.Connect(endpoint);
                socket.SendTimeout = timeout;
                socket.ReceiveTimeout = timeout;
                socket.Send(request);
                socket.Receive(response);
                socket.Close();

                const byte replytime = 40;
                ulong secondspart = BitConverter.ToUInt32(response, replytime);
                ulong secondsfraction = BitConverter.ToUInt32(response, replytime + 4);
                secondspart = SwapEndian(secondspart);
                secondsfraction = SwapEndian(secondsfraction);
                ulong milliseconds = (secondspart * 1000) + ((secondsfraction * 1000) / 0x100000000UL);

                var utc = (new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds(milliseconds);
                return new Class<DateTime>(utc);
            }
            catch { }
            return null;
        }

        private static uint SwapEndian(ulong x)
        {
            var a = ((x & 0x000000ff) << 24);
            var b = ((x & 0x0000ff00) << 8);
            var c = ((x & 0x00ff0000) >> 8);
            var d = ((x & 0xff000000) >> 24);
            return (uint)(a + b + c + d);
        }

        #endregion

        #region Puny Code

        /// <summary></summary>
        public static string ToPunyCode(string chinese)
        {
            if (string.IsNullOrEmpty(chinese)) return "";
            try { return new IdnMapping().GetAscii(chinese); }
            catch { return chinese; }
        }

        /// <summary></summary>
        public static string FromPunyCode(string punycode)
        {
            if (string.IsNullOrEmpty(punycode)) return "";
            try { return new IdnMapping().GetUnicode(punycode); }
            catch { return punycode; }
        }

        #endregion

        #region IP

        /// <summary>获取本地计算机的计算机名。</summary>
        public static string LocalHost
        {
            get => Dns.GetHostName() ?? "";
        }

        /// <summary>本地计算机的所有 IP 地址。</summary>
        public static IPAddress[] LocalIP
        {
            get => Dns.GetHostEntry(Dns.GetHostName()).AddressList;
        }

        /// <summary>判断 IPv4 地址格式是否正确。</summary>
        public static bool IsIP(string ipv4)
        {
            try
            {
                if (string.IsNullOrEmpty(ipv4)) return false;

                var split = ipv4.Split('.');
                if (split.Length != 4) return false;

                for (int i = 0; i < 4; i++)
                {
                    var n = Convert.ToInt32(split[i]);
                    if (n < 0 || n > 255) return false;
                    if (n.ToString() != split[i]) return false;
                }
                return true;
            }
            catch { }
            return false;
        }

        /// <summary>对目标地址进行解析。</summary>
        public static string Resolve(string host)
        {
            try
            {
                if (IsIP(host))
                {
                    var ip = IPAddress.Parse(host);
                    var he = Dns.GetHostEntry(ip);
                    return he.HostName;
                }
                else
                {
                    var ip = "";
                    var dn = host.ToLower();
                    var he = Dns.GetHostEntry(dn);
                    var ts = "";
                    var on = he.Aliases;
                    he = Dns.GetHostEntry(dn);
                    for (int i = 0; i < on.Length; i++) ts = ts + on[i].ToString() + ",";
                    ts = "";
                    var al = he.AddressList;
                    for (int i = 0; i < al.Length; i++) ts = ts + al[i].ToString() + ",";
                    ip = ts;
                    if (ip.Length > 0)
                    {
                        if (ip.Substring(ip.Length - 1, 1) == ",") ip = ip.Substring(0, ip.Length - 1);
                    }
                    return ip;
                }
            }
            catch { }
            return "";
        }

        /// <summary>将由字符串表示的 IPv4 地址转换为 32 位整数。</summary>
        public static int GetNumber(IPAddress ipv4)
        {
            try
            {
                var ba = ipv4.GetAddressBytes();
                return BitConverter.ToInt32(ba, 0);
            }
            catch { return 0; }
        }

        /// <summary>转换 IP 地址格式。</summary>
        public static string GetPlainAddress(IPAddress address)
        {
            try { return address.ToString(); }
            catch { return ""; }
        }

        /// <summary>转换 IP 地址格式。</summary>
        public static string GetPlainAddress(IPEndPoint endpoint)
        {
            try { return GetPlainAddress(endpoint.Address); }
            catch { return ""; }
        }

        /// <summary>转换 IP 地址格式。</summary>
        public static IPAddress GetIPAddress(string address)
        {
            try { return IPAddress.Parse(address); }
            catch { return new IPAddress(0); }
        }

        /// <summary>转换 IP 地址格式。</summary>
        public static IPEndPoint GetIPEndPoint(string address, int port)
        {
            try { return new IPEndPoint(IPAddress.Parse(address), NumberUtility.Restrict(port, 0, ushort.MaxValue)); }
            catch { return new IPEndPoint(0, 0); }
        }

        /// <summary>转换 IP 地址格式。</summary>
        public static IPEndPoint GetIPEndPoint(IPAddress address, int port)
        {
            try { return new IPEndPoint(address, NumberUtility.Restrict(port, 0, ushort.MaxValue)); }
            catch { return new IPEndPoint(0, 0); }
        }

        /// <summary>转换 IP 地址格式。</summary>
        public static IPEndPoint GetIPEndPoint(System.Net.EndPoint endpoint)
        {
            try { return (IPEndPoint)endpoint; }
            catch { return new IPEndPoint(0, 0); }
        }

        /// <summary>判断私有 IP 地址。</summary>
        public static bool FromLAN(string ipv4)
        {
            if (ipv4.IsEmpty()) return false;

            // localhost
            if (ipv4 == "::1") return true;
            if (ipv4 == "127.0.0.1") return true;

            // IPv4
            var a = ipv4.Split('.');
            if (a.Length != 4) return false;
            switch (a[0])
            {
                case "10":
                    return true;
                case "172":
                    var a1 = NumberUtility.Int32(a[1]);
                    if (a1 >= 16 && a1 <= 31) return true;
                    break;
                case "192":
                    if (a[1] == "168") return true;
                    break;
            }

            return false;
        }

        #endregion

        #region ICMP

        /// <summary>发送 PING 命令，命令中包含 32 位零数据。</summary>
        /// <param name="address">目标地址。</param>
        /// <param name="timeout">等待响应的超时时间（毫秒）。</param>
        /// <param name="ttl">命令的起始 TTL 值（在丢弃数据之前可以转发该数据的路由节点数）。</param>
        /// <param name="df">是否分段。</param>
        /// <returns>命令的返回结果。</returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="PingException"></exception>
        public static PingReply Ping(string address, int timeout = 1000, byte ttl = 255, bool df = true)
        {
            if (string.IsNullOrEmpty(address)) throw new ArgumentNullException(nameof(address));
            if (timeout < 1) throw new ArgumentOutOfRangeException(nameof(timeout));

            var ip = IsIP(address) ? address : Resolve(address);
            if (ip.Contains(",")) ip = ip.Split(',')[0];

            var options = new PingOptions();
            options.DontFragment = df;
            options.Ttl = ttl;

            var buffer = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            var ping = new Ping();
            var reply = ping.Send(ip, timeout, buffer, options);
            return reply;
        }

        #endregion

        #region HTTP

        /// <summary>解析 HTTP 方法。</summary>
        public static HttpMethod ParseHttpMethod(string method)
        {
            if (!string.IsNullOrEmpty(method))
            {
                var upper = TextUtility.Upper(method);
                if (upper.Contains("OPTIONS")) return HttpMethod.OPTIONS;
                else if (upper.Contains("POST")) return HttpMethod.POST;
                else if (upper.Contains("GET")) return HttpMethod.GET;
                else if (upper.Contains("CONNECT")) return HttpMethod.CONNECT;
                else if (upper.Contains("DELETE")) return HttpMethod.DELETE;
                else if (upper.Contains("HEAD")) return HttpMethod.HEAD;
                else if (upper.Contains("PATCH")) return HttpMethod.PATCH;
                else if (upper.Contains("PUT")) return HttpMethod.PUT;
                else if (upper.Contains("TRACE")) return HttpMethod.TRACE;
            }
            return HttpMethod.NULL;
        }

        /// <summary>GET</summary>
        public static HttpClient HttpGet(string url, int timeout = 30000) => HttpClient.Get(url, timeout);

        /// <summary>POST</summary>
        public static HttpClient HttpPost(string url, byte[] data, int timeout = 30000, string type = "application/octet-stream") => HttpClient.Post(url, data, timeout, type);

        /// <summary>POST text/plain</summary>
        public static HttpClient HttpPost(string url, string text, int timeout = 30000, string type = "text/plain") => HttpClient.Text(url, text, timeout, type);

        /// <summary>POST application/x-www-form-urlencoded</summary>
        public static HttpClient HttpPost(string url, IDictionary<string, string> form, int timeout = 30000) => HttpClient.Form(url, form, timeout);

        /// <summary>POST application/x-www-form-urlencoded</summary>
        public static HttpClient HttpPost(string url, Dictionary<string, string> form, int timeout = 30000) => HttpClient.Form(url, form, timeout);

        /// <summary>获取 HTTP 状态的文本。</summary>
        public static string HttpStatusDescription(int code)
        {
            switch (code)
            {
                case 100: return "Continue";
                case 101: return "Switching Protocols";
                case 102: return "Processing";
                case 200: return "OK";
                case 201: return "Created";
                case 202: return "Accepted";
                case 203: return "Non-Authoritative Information";
                case 204: return "No Content";
                case 205: return "Reset Content";
                case 206: return "Partial Content";
                case 207: return "Multi-Status";
                case 300: return "Multiple Choices";
                case 301: return "Moved Permanently";
                case 302: return "Found";
                case 303: return "See Other";
                case 304: return "Not Modified";
                case 305: return "Use Proxy";
                case 307: return "Temporary Redirect";
                case 400: return "Bad Request";
                case 401: return "Unauthorized";
                case 402: return "Payment Required";
                case 403: return "Forbidden";
                case 404: return "Not Found";
                case 405: return "Method Not Allowed";
                case 406: return "Not Acceptable";
                case 407: return "Proxy Authentication Required";
                case 408: return "Request Timeout";
                case 409: return "Conflict";
                case 410: return "Gone";
                case 411: return "Length Required";
                case 412: return "Precondition Failed";
                case 413: return "Request Entity Too Large";
                case 414: return "Request-Uri Too Long";
                case 415: return "Unsupported Media Type";
                case 416: return "Requested Range Not Satisfiable";
                case 417: return "Expectation Failed";
                case 422: return "Unprocessable Entity";
                case 423: return "Locked";
                case 424: return "Failed Dependency";
                case 426: return "Upgrade Required"; // RFC 2817
                case 500: return "Internal Server Error";
                case 501: return "Not Implemented";
                case 502: return "Bad Gateway";
                case 503: return "Service Unavailable";
                case 504: return "Gateway Timeout";
                case 505: return "Http Version Not Supported";
                case 507: return "Insufficient Storage";
                default: return null;
            }
        }

        /// <summary>按文件扩展名获取 Content-Type 值。</summary>
        public static string Mime(string extension)
        {
            const string Default = "application/octet-stream";
            if (string.IsNullOrEmpty(extension)) return Default;

            var split = extension.Split('.');
            var lower = split.Length < 1 ? null : split[split.Length - 1].Lower();
            switch (lower)
            {
                // text/plain; charset=utf-8
                case "css": return "text/css";
                case "htm": return "text/html";
                case "html": return "text/html";
                case "ini": return "text/ini";
                case "js": return "application/javascript";
                case "json": return "text/json";
                case "shtml": return "text/html";
                case "sh": return "text/plain";
                case "txt": return "text/plain";
            }
            switch (lower)
            {
                case "jad": return "text/vnd.sun.j2me.app-descriptor";
                case "m3u8": return "text/vnd.apple.mpegurl"; // application/vnd.apple.mpegurl
                case "xml": return "text/xml";
                case "htc": return "text/x-component";
                case "mml": return "text/mathml";
                case "wml": return "text/vnd.wap.wml";
            }
            switch (lower)
            {
                case "3gp": return "video/3gpp";
                case "3gpp": return "video/3gpp";
                case "7z": return "application/x-7z-compressed";
                case "ai": return "application/postscript";
                case "asf": return "video/x-ms-asf";
                case "asx": return "video/x-ms-asf";
                case "atom": return "application/atom+xml";
                case "avi": return "video/x-msvideo";
                case "bmp": return "image/x-ms-bmp";
                case "cco": return "application/x-cocoa";
                case "crt": return "application/x-x509-ca-cert";
                case "der": return "application/x-x509-ca-cert";
                case "doc": return "application/msword";
                case "docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                case "ear": return "application/java-archive";
                case "eot": return "application/vnd.ms-fontobject";
                case "eps": return "application/postscript";
                case "flv": return "video/x-flv";
                case "gif": return "image/gif";
                case "hqx": return "application/mac-binhex40";
                case "ico": return "image/x-icon";
                case "jar": return "application/java-archive";
                case "jardiff": return "application/x-java-archive-diff";
                case "jng": return "image/x-jng";
                case "jnlp": return "application/x-java-jnlp-file";
                case "jpeg": return "image/jpeg";
                case "jpg": return "image/jpeg";
                case "kar": return "audio/midi";
                case "kml": return "application/vnd.google-earth.kml+xml";
                case "kmz": return "application/vnd.google-earth.kmz";
                case "m4a": return "audio/x-m4a";
                case "m4v": return "video/x-m4v";
                case "mid": return "audio/midi";
                case "midi": return "audio/midi";
                case "mkv": return "video/x-matroska";
                case "mng": return "video/x-mng";
                case "mov": return "video/quicktime";
                case "mp3": return "audio/mpeg";
                case "mp4": return "video/mp4";
                case "mpeg": return "video/mpeg";
                case "mpg": return "video/mpeg";
                case "odg": return "application/vnd.oasis.opendocument.graphics";
                case "odp": return "application/vnd.oasis.opendocument.presentation";
                case "ods": return "application/vnd.oasis.opendocument.spreadsheet";
                case "odt": return "application/vnd.oasis.opendocument.text";
                case "ogg": return "audio/ogg";
                case "pdb": return "application/x-pilot";
                case "pdf": return "application/pdf";
                case "pem": return "application/x-x509-ca-cert";
                case "pl": return "application/x-perl";
                case "pm": return "application/x-perl";
                case "png": return "image/png";
                case "ppt": return "application/vnd.ms-powerpoint";
                case "pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
                case "prc": return "application/x-pilot";
                case "ps": return "application/postscript";
                case "ra": return "audio/x-realaudio";
                case "rar": return "application/x-rar-compressed";
                case "rpm": return "application/x-redhat-package-manager";
                case "rss": return "application/rss+xml";
                case "rtf": return "application/rtf";
                case "run": return "application/x-makeself";
                case "sea": return "application/x-sea";
                case "sit": return "application/x-stuffit";
                case "svg": return "image/svg+xml";
                case "svgz": return "image/svg+xml";
                case "swf": return "application/x-shockwave-flash";
                case "tcl": return "application/x-tcl";
                case "tif": return "image/tiff";
                case "tiff": return "image/tiff";
                case "tk": return "application/x-tcl";
                case "ts": return "video/mp2t";
                case "war": return "application/java-archive";
                case "wbmp": return "image/vnd.wap.wbmp";
                case "webm": return "video/webm";
                case "webp": return "image/webp";
                case "wmlc": return "application/vnd.wap.wmlc";
                case "wmv": return "video/x-ms-wmv";
                case "woff": return "font/woff";
                case "woff2": return "font/woff2";
                case "xhtml": return "application/xhtml+xml";
                case "xls": return "application/vnd.ms-excel";
                case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                case "xpi": return "application/x-xpinstall";
                case "xspf": return "application/xspf+xml";
                case "zip": return "application/zip";
            }
            return Default;
        }

        #endregion

        #region Port

        private static int[] ActivePorts(IPEndPoint[] endpoints)
        {
            var list = new List<int>(endpoints.Length);
            foreach (var endpoint in endpoints)
            {
                var port = endpoint.Port;
                if (list.Contains(port)) continue;
                list.Add(port);
            }
            list.Sort();
            list.Capacity = list.Count;
            return list.ToArray();
        }

        /// <summary>列出活动的 TCP 端口。</summary>
        public static int[] ActiveTcpPorts() => ActivePorts(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());

        /// <summary>列出活动的 UDP 端口。</summary>
        public static int[] ActiveUdpPorts() => ActivePorts(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());

        #endregion

    }

}
